#include "common.hpp"

static const char *optString = "k:h?";

static const struct option longOpts[] = {
	{ "help", no_argument, NULL, 'h' },
	{ "key", required_argument, NULL, 'n' },
	{ 0,0,0,0 }
};

template <typename ID>
class RegistroDePersonas {
	public:
		typedef std::set<ID> tPersonas;

	protected:
		tPersonas disponibles;
		tPersonas ocupados;

	public:
		inline bool registrar (const ID& persona) {
			return disponibles.insert(persona).second;
		}

		inline bool ocupar (const ID& persona) {
			bool exito = ( disponibles.erase(persona) > 0 );
			if (exito)
				exito = ( ocupados.insert(persona).second );
			return exito;
		}

		inline bool liberar (const ID& persona) {
			bool exito = ( ocupados.erase(persona) > 0 );
			if (exito)
				exito = ( disponibles.insert(persona).second );
			return exito;
		}

		inline bool salir (const ID& persona) {
			bool exito = ( ocupados.count(persona) == 0 );
			if (exito)
				exito = ( disponibles.erase(persona) > 0 );
			return exito;
		}

		inline const tPersonas getDisponibles() const {
			return this->disponibles;
		}

		inline const tPersonas getOcupados() const {
			return this->ocupados;
		}

		inline bool isRegistrado(const ID& persona) const {
			return ( (disponibles.count(persona) + ocupados.count(persona)) > 0 );
		}

		inline virtual const ID& elegirPersonaDisponible() const {
			size_t aux = rand()%disponibles.size();
			static ID id;
			id = 0;
			if ( !disponibles.empty() ) {
				typename tPersonas::const_iterator it = disponibles.begin();
				while (aux-- > 0)
					it++;
				id = *it;
			}
			return id;
		}
}; // class RegistroDePersonas;
	
int main (int argc, char* const argv[]) {
	// Objetos IPC
//	ipc::bounded_counter_with_exceptions lugares( ftok(BASE_NAME,11), ftok(BASE_NAME,10),0 );
//	ipc::semaphore_with_exceptions mutex( ftok(BASE_NAME,10),0 );
//	ipc::shared_memory_with_exceptions<sGimnasio> gim( ftok(BASE_NAME,11) );
	ipc::bounded_counter_with_exceptions entrenadores_disponibles( ftok(BASE_NAME,21), ftok(BASE_NAME,20),0);

	std::stringstream ss;
	sMensaje msg;

	// Parametros
	key_t clave = 0;

//	size_t actual = 0;
	int longIndex = 0;
	int option = 0;
	bool seguir = true;
	bool ayuda = false;
	bool responder = false;

	// Interpretación de parametros.
	while ( (option = getopt_long(argc,argv,optString,longOpts,&longIndex) ) != EOF ) {
		switch (option) {
			case '?':
			case 'h':
			case ':':
				ayuda = true;
				break;
			case 'k':
				clave = parse<key_t>(optarg,0);
				break;
			default:
				break;
		}
	}

	if ( ayuda || clave == 0) {
		std::cerr << "Proceso de control de entradas y salidas de entrenadores y clientes del gimnasio." << std::endl;
		std::cerr << "\tUso: " << argv[0] << " [-h] <-k natural>" << std::endl;
		std::cerr << "\tParametros:" << std::endl;
		std::cerr << "\t\t-h,--help:\tMostrar este mensaje de ayuda." << std::endl;
		std::cerr << "\t\t-k,--key:\tClave de la cola de mensajes asociada." << std::endl;

		return (ayuda) ? 0 : -1;
	}

	logger.setColor("\033[2;32m");
	try {
		// Creo la cola de mensajes aqui por que necesito utilizar la clave por parametro.
		ipc::messages_queue_with_exceptions mq(clave);

		entrenadores_disponibles.get(PERMITS);

		mq.get(PERMITS);

		logger << "Recepcionista: Inicio." << nl;
		RegistroDePersonas<pid_t> entrenadores;
		RegistroDePersonas<pid_t> clientes;
		
		while (seguir) {
			msg.data = '\0';
			mq.receive(msg,1);

			responder = false;
			if (msg.data == 'R') {
				// Agrego el identificador del entrenador a los disponibles.
				if ( entrenadores.registrar(msg.origen) ) {
					logger << "Recepcionista: El entrenador " << msg.origen << " se ha registrado." << nl;
					entrenadores_disponibles.inc(); // TODO FIX
				}
				else
					error << "Recepcionista: El entrenador " << msg.origen << " se registro dos veces." << nl;
				responder = true;

			} else if (msg.data == 'O') {
				if ( entrenadores.ocupar(msg.origen) ) {
					logger << "Recepcionista: El entrenador " << msg.origen << " se ha saturado." << nl;
					entrenadores_disponibles.dec();
				}
				else
					error << "Recepcionista: El entrenador " << msg.origen << " no esta disponible." << nl;
				responder = true;

			} else if ( msg.data == 'L') {
				if ( entrenadores.liberar(msg.origen) ) {
					logger << "Recepcionista: El entrenador " << msg.origen << " se ha liberado. " << nl;
					entrenadores_disponibles.inc();
				}
				else
					error << "Recepcionista: El entrenador " << msg.origen << " no está saturado. " << nl;
				responder = true;

			} else if (msg.data == 'E') {
				// Agrego el identificador del cliente a los disponibles.
				if ( clientes.registrar(msg.origen) )
					logger << "Recepcionista: El cliente " << msg.origen << " se ha anunciado en recepción." << nl;
				else
					error << "Recepcionista: El cliente " << msg.origen << " se ha anunciado dos veces." << nl;
				responder = true;

			} else if (msg.data == 'S') {
				if ( entrenadores.isRegistrado(msg.origen) ) {
					if ( entrenadores.salir(msg.origen) )
						logger << "Recepcionista: El entrenador " << msg.origen << " se retira." << nl;
					else
						error << "Recepcionista: El entrenador " << msg.origen << " no puede retirarse." << nl;
				}
				if ( clientes.isRegistrado(msg.origen) ) {
					if ( clientes.salir(msg.origen) ) 
						logger << "Recepcionista: El cliente " << msg.origen << " se retira." << nl;
					else {
						error << "Recepcionista: El cliente " << msg.origen << " se retira incorrectamente." << nl;
						// El cliente siempre tiene la razón.
						clientes.liberar(msg.origen);
						clientes.salir(msg.origen);
					}
				}
				responder = true;
			}

			if (responder) {
				// La respuesta en el caso principal es identica al mensaje entrante.
				msg.tipo = msg.origen;
				msg.origen = 1;

				// En caso de ser la entrada de un cliente, se debe conseguir un entrenador. Si no hay se debe pedirle al cliente que se vaya.
				if (msg.data == 'E') {
					logger << "Recepcionista: Seleccionando entrenador." << nl;
					pid_t entrenador = entrenadores.elegirPersonaDisponible();

					if (entrenador > 0) {
						msg.origen = entrenador;
						logger << "Recepcionista: El cliente " << msg.tipo << " es asignado al entrenador " << msg.origen << "." << nl;
					} else {
						// Si no lo puedo atender le pido que se vaya.
						msg.data = 'S';
						logger << "Recepcionista: No hay entrenadores disponibles." << nl;
					}
					mq.send(msg);
				}

				mq.send(msg);				
			}
		}
		logger << "Recepcionista: Fin." << nl;
	} catch (ipc::exception& e) {
		logger << "Recepcionista: " << e.what() << "." << nl;
	}

	return 0;
}
